package lumis.portal.serialization;

import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidParameterException;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;

import lumis.portal.PortalException;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.progress.ProgressConfig;
import lumis.portal.progress.ProgressConfig.Type;
import lumis.portal.serialization.persistency.IDirectory;
import lumis.portal.transaction.ITransaction;
import lumis.util.IResource;
import lumis.util.TextUtil;
import lumis.util.log.ILogger;
import lumis.util.log.LoggerFactory;

/**
 * Base class for Serialization Context classes
 *
 * @version $Revision: 12710 $ $Date: 2011-03-18 17:00:16 -0300 (Fri, 18 Mar 2011) $
 * @since 4.0.7
 */
public class BaseSerializationContext
{
	private static ILogger logger = LoggerFactory.getServiceLogger("lumis.service.portalmanagement.serialization");

	private final SessionConfig sessionConfig;
	private IDirectory directory;
	private ITransaction transaction;
	private String rootChannelId;
	private SerializationSettings serializationSettings = new SerializationSettings();
	private final IResource resource = new SerializationStringResource();
	private String processId;
	private final boolean progressEnabled;
	private final boolean includeContent;
	private int overallProgressMax = 1;
	private int overallProgressValue = 0;
	private int itemProgressMax = 1;
	private int itemProgressValue = 0;
	private String currentChannelPath;
	private long startTime = new Date().getTime();
	private ISerializer defaultSerializer;
	private Set<String> globalGroups = new HashSet<String>();
	private boolean shallowSerialization = false;

	public BaseSerializationContext(final SessionConfig sessionConfig, 
			final lumis.util.ITransaction transaction, String rootChannelId, 
			IDirectory directory, boolean progressEnabled, boolean includeContent)
	{
		this.sessionConfig = sessionConfig;
		setTransaction(transaction);
		this.directory = directory;
		this.rootChannelId = rootChannelId;
		this.progressEnabled = progressEnabled;
		this.includeContent = includeContent;
	}
	
	/**
	 * Returns the global groups for serializing.
	 * These global groups will be added to the destination when imported.
	 * 
	 * @return the global groups to be serialized.
	 * @since 4.2.1
	 */
	public Set<String> getGlobalGroups()
	{
		return globalGroups;
	}

	public SerializationSettings getSerializationSettings()
	{
		return serializationSettings;
	}

	public void setSerializationSettings(SerializationSettings serializationSettings)
	{
		this.serializationSettings = serializationSettings;
	}

	public String getRootChannelId()
	{
		return rootChannelId;
	}

	public void setRootChannelId(String rootChannelId)
	{
		this.rootChannelId = rootChannelId;
	}

	public IDirectory getDirectory()
	{
		return directory;
	}

	public void setDirectory(IDirectory directory)
	{
		this.directory = directory;
	}

	public SessionConfig getSessionConfig()
	{
		return sessionConfig;
	}

	public void setTransaction(lumis.util.ITransaction transaction)
	{
		this.transaction = (ITransaction) transaction;
	}

	public ITransaction getTransaction()
	{
		return transaction;
	}

	public IResource getResource()
	{
		return resource;
	}

	public String getProcessId()
	{
		return processId;
	}

	public void setProcessId(String processId)
	{
		this.processId = processId;
	}

	public int getItemProgressMax()
	{
		return itemProgressMax;
	}

	public void setItemProgressMax(int itemProgressMax)
	{
		this.itemProgressMax = itemProgressMax;
	}

	public int getItemProgressValue()
	{
		return itemProgressValue;
	}

	public void setItemProgressValue(int itemProgressValue)
	{
		this.itemProgressValue = itemProgressValue;
	}

	public void incrementItemProgressValue(int increment)
	{
		this.itemProgressValue += increment;
	}

	public int getOverallProgressMax()
	{
		return overallProgressMax;
	}

	public void setOverallProgressMax(int overallProgressMax)
	{
		this.overallProgressMax = overallProgressMax;
	}

	public void incrementOverallProgressValue(int increment)
	{
		this.overallProgressValue += increment;
	}
	
	public int getOverallProgressValue()
	{
		return overallProgressValue;
	}

	public void setOverallProgressValue(int overallProgressValue)
	{
		this.overallProgressValue = overallProgressValue;
	}
	
	public long getStartTime()
	{
		return startTime;
	}

	public void setStartTime(long startTimeMillis)
	{
		this.startTime = startTimeMillis;
	}

	public boolean getIncludeContent()
	{
		return includeContent;
	}

	public boolean getProgressEnabled()
	{
		return progressEnabled;
	}

	private void updateProgress(ProgressConfig progressConfig) throws PortalException
	{
		logDebug(progressConfig.getMessage());
		if (progressEnabled)
		{
			if (!progressConfig.isLastMessage() && ManagerFactoryInternal.getProgressManager().getAbortedByProcessId(progressConfig.getProcessId()))
				throw new SerializationAbortedException("STR_OPERATION_CANCELLED_BY_USER");
			String message = progressConfig.getMessage();
			if (message != null)
				message = ManagerFactory.getLocalizationManager().localize(message, resource);
			progressConfig.setMessage(message);
			progressConfig.setElapsedTime((int)(new Date(). getTime() - startTime));
			ManagerFactoryInternal.getProgressManager().addOrUpdate(progressConfig);
		}
	}

	public void updateProgressBar(int incrementOverallProgress, String itemName) throws PortalException
	{
		this.overallProgressValue += incrementOverallProgress;
		this.itemProgressMax = 0;
		this.itemProgressValue = 0;
		updateProgress(new ProgressConfig(processId, overallProgressMax, overallProgressValue, itemProgressMax, itemProgressValue, itemName, Type.STEP, false));
	}
	
	public void updateProgressBar(int incrementOverallProgress, int incrementItemProgress, String itemName) throws PortalException
	{
		this.overallProgressValue += incrementOverallProgress;
		this.itemProgressValue += incrementItemProgress;
		updateProgress(new ProgressConfig(processId, overallProgressMax, overallProgressValue, itemProgressMax, itemProgressValue, itemName, Type.STEP, false));
	}
	
	public void sendProgressNotification(String message, Type type, String innerProgressId) throws PortalException
	{
		if (type.equals(Type.STEP))
			throw new InvalidParameterException("Type.STEP is invalid. Use another type.");
		ProgressConfig progressConfig = new ProgressConfig(processId, overallProgressMax, overallProgressValue, itemProgressMax, itemProgressValue, message, type, false);
		progressConfig.setInnerProgressId(innerProgressId);
		updateProgress(progressConfig);
	}

	public void sendProgressNotification(Document messageDoc, String styleFilename, Type type) throws PortalException
	{
		sendProgressNotification(transformAndLocalizeNotificationMessage(messageDoc, styleFilename), type, null);
	}
	
	public void sendProgressFinalNotification(String message, Type type) throws PortalException
	{
		updateProgress(new ProgressConfig(processId, 0, 0, 0, 0, message, type, true));
	}

	public String getCurrentChannelPath()
	{
		return currentChannelPath;
	}

	public void setCurrentChannelPath(String currentChannelPath)
	{
		this.currentChannelPath = currentChannelPath;
	}
	
	private String transformAndLocalizeNotificationMessage(Document messageDoc, String styleFilename) throws PortalException
	{
		String localizedMessage = null;
		String xslFilePath = styleFilename;
		ByteArrayOutputStream transformedStream = null;
		String message = null;
		try
		{
			Transformer transformer = ManagerFactoryInternal.getXslCacheManager().getTransformer(sessionConfig, xslFilePath, transaction);
			DOMSource domSource = new DOMSource(messageDoc);
			transformedStream = new ByteArrayOutputStream();
			transformer.transform(domSource, new StreamResult(transformedStream));
			try
			{
				message = transformedStream.toString("UTF-8");
			}
			catch (UnsupportedEncodingException e)
			{
				throw new SerializationException("STR_ERROR_ENCODING_XML", resource, e);
			}
		}
		catch (TransformerException e)
		{
			throw new SerializationException("STR_ERROR_TRANSFORMING_NOTIFICATION_MESSAGE;" + xslFilePath, resource, e);
		}
		finally
		{
			if (transformedStream != null)
			{
				try
				{
					transformedStream.close();
				}
				catch (Exception e)
				{
				}
			}
		}
		try
		{
			if (message != null)
				localizedMessage = ManagerFactory.getLocalizationManager().localize(message, resource, getSessionConfig().getLocale());
		}
		catch (Exception e)
		{
			throw new SerializationException("STR_ERROR_TRANSFORMING_NOTIFICATION_MESSAGE", e);
		}
		
		return localizedMessage;
	}

	public void logDebug(String msg)
	{
		if (logger.isDebugEnabled())
			logger.debug(getLogMessage(msg, null));
	}

	public void logDebug(String msg, Object o)
	{
		if (logger.isDebugEnabled())
			logger.debug(getLogMessage(msg, o));
	}

	public void logDebug(String msg, Throwable t)
	{
		if (logger.isDebugEnabled())
			logger.debug(getLogMessage(msg, null), t);
	}
	
	private String getLogMessage(String msg, Object o)
	{
		if (msg == null)
			msg = "";
		
		String serializedObject = "";
		try
		{
			if (o != null)
				serializedObject = "[Serialized object] " + ManagerFactoryInternal.getSerializationManager2().serializeObjectToString(o, null).replace("\n", " ").replaceAll(">\\s+?<", "><");
		}
		catch (Throwable e)
		{
			serializedObject = "[Error serializing object of class '" + o.getClass().getName() + "' (" + e + ")]";
		}
		return getProcessId() + TextUtil.stringRepeat('\t', Thread.currentThread().getStackTrace().length - 3) + msg.replace("\n", " ").replaceAll(">\\s+?<", "><") + serializedObject;
	}

	/**
	 * Returns the default object serializer for this serialization context.
	 * @return the default object serializer.
	 * @since 5.0.0
	 */
	public ISerializer getDefaultSerializer()
	{
		return defaultSerializer;
	}

	/**
	 * Sets the default object serializer for this serialization context.
	 * @param defaultSerializer the defaultSerializer value to set.
	 * @since 5.0.0
	 */
	public void setDefaultSerializer(ISerializer defaultSerializer)
	{
		this.defaultSerializer = defaultSerializer;
	}
	
	/**
	 * Sets whether the serialization will serialize the structure recursively or not.
	 * See {@link #isShallowSerialization()} for more details on recursion implications.
	 *
	 * @param shallowSerialization the shallow serialization value.
	 * @since 5.0.0
	 */
	protected void setShallowSerialization(boolean shallowSerialization)
	{
		this.shallowSerialization = shallowSerialization;
	}
	
	/**
	 * Returns whether the serialization is recursive or not.
	 * When false all sub-channels are recursively serialized.  When true only the
	 * root channel and its pages, service instances, local groups, page templates, acls,
	 * and all the globally referenced css files, interface instance xsl files and global
	 * groups are exported.
	 * 
	 * @return true if shallow serialization is set, false if serialization is recursive.
	 * @since 5.0.0
	 */
	public boolean isShallowSerialization()
	{
		return this.shallowSerialization;
	}
}
